package ray;

import java.util.Arrays;
import java.util.Comparator;
import ray.math.Point3;
import ray.math.Vector3;
import ray.surface.Box;
import ray.surface.Surface;
/**
 * Axis-Aligned-Bounding-Box to speed up the intersection look up time.
 *
 */
public class AABB {
    boolean isLeaf;

    /** The current bounding box for this tree node.
     *  The bounding box is described by
     *  (minPt.x, minPt.y, minPt.z) - (maxBound.x, maxBound.y, maxBound.z). */
    Point3 minBound, maxBound;

    /** child[0] is the left child. child[1] is the right child. */
    AABB []child;

    /** A shared surfaces array that will be used across every node in the tree. */
    static Surface []surfaces;

    /** left and right are indices for []surfaces,
     * meaning that this tree node contains a set of surfaces
     * from surfaces[left] to surfaces[right-1]. */
    int left, right;

    /** A comparator class that can sort surfaces by x, y, or z coordinate. */
   static MyComparator cmp = new MyComparator();


    public AABB(boolean isLeaf, Point3 minBound, Point3 maxBound, AABB leftChild, AABB rightChild, int left, int right) {
        this.isLeaf = isLeaf;
        this.minBound = minBound;
        this.maxBound = maxBound;
        this.child = new AABB[2];
        this.child[0] = leftChild;
        this.child[1] = rightChild;
        this.left = left;
        this.right = right;
    }

    public AABB() {    }

    /**
     * Set the shared static surfaces that every node in the tree will use by
     * using left and right indices.
     */
    public static void setSurfaces(Surface []surfaces) {
        AABB.surfaces = surfaces;
    }

    /**
     * Create an AABB [sub]tree.  This tree node will be responsible for storing
     * and processing surfaces[left] to surfaces[right-1].
     * @param left The left index of []surfaces
     * @param left The right index of []surfaces
     */
    public static AABB createTree(int left, int right) {

        // Find out the BIG bounding box enclosing all the surfaces in the range [left, right)
        // and store them in minB and maxB.
        // Hint: To find the bounding box for each surface, use getMinBound() and getMaxBound() */
        Point3 minB = new Point3(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
        Point3 maxB = new Point3(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
        for(int i = left; i < right; i++)
        {
            minB.x = Math.min(minB.x,surfaces[i].getMinBound().x);
            minB.y = Math.min(minB.y,surfaces[i].getMinBound().y);
            minB.z = Math.min(minB.z,surfaces[i].getMinBound().z);

            maxB.x = Math.max(maxB.x,surfaces[i].getMaxBound().x);
            maxB.y = Math.max(maxB.y,surfaces[i].getMaxBound().y);
            maxB.z = Math.max(maxB.z,surfaces[i].getMaxBound().z);
        }

        // Check for the base case.
        // If the range [left, right) is small enough, just return a new leaf node.
        if(right - left <= 1)
            return new AABB(true,
                    surfaces[left].getMinBound(),
                    surfaces[left].getMaxBound(),
                    null,null,left,left);

        // Figure out the widest dimension (x or y or z).
        // If x is the widest, set widestDim = 0. If y, set widestDim = 1. If z, set widestDim = 2.
        int widestDim = 0;
        double widestVal = maxB.x - minB.x;
        double yDiff = maxB.y - minB.y;
        double zDiff = maxB.z - minB.z;

        if(yDiff > widestVal)
            {widestVal = yDiff; widestDim = 1;}
        if(zDiff > widestVal)
            widestDim = 2;

        // Sort surfaces according to the widest dimension.
        cmp.setIndex(widestDim);
        Arrays.sort(surfaces, left, right, cmp);

        // Recursively create left and right children.
        int mid = (left+right)/2;
        AABB leftChild = createTree(left,mid);
        AABB rightChild = createTree(mid,right);

        return new AABB(false, minB, maxB, leftChild, rightChild, left, right);
    }

    public boolean intersect(Intersection outRecord, Ray rayIn, boolean anyIntersection) {

        Intersection record = (anyIntersection ? new Intersection() : outRecord);

        if(isLeaf)
        {
            for(Surface s : surfaces)
                if(s.intersect(record, rayIn)) 
                    return true;
            return false;
        }
        return ((child[0].intersect(record,rayIn,anyIntersection)) ||
                (child[0].intersect(record,rayIn,anyIntersection)));
    }

    /**
     * Check if the ray intersects this bounding box.
     * @param ray
     * @return true if ray intersects this bounding box
     */
    @SuppressWarnings("unused")
	private boolean isIntersect(Ray ray) {
       
        Box b = new Box();
        b.setMinPt(minBound);
        b.setMaxPt(maxBound);

        Intersection record = new Intersection();

        return b.intersect(record, ray);
    }
}

class MyComparator implements Comparator<Surface> {
    int index;
    public MyComparator() {    }

    public void setIndex(int index) {
        this.index = index;
    }

    public int compare(Surface o1, Surface o2) {
        double v1 = o1.getAveragePosition().getE(index);
        double v2 = o2.getAveragePosition().getE(index);
        if(v1 < v2) return 1;
        if(v1 > v2) return -1;
        return 0;
    }

}
